﻿using System;
using System.Data;
using System.Text;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
    /// <summary>Extends <see cref="IDbColumn"/> objects.</summary>
    /// <remarks>Provides methods for the creation of the compatibile ADO.NET <see cref="DataColumn"/> objects, 
    /// validation (see <see cref="Validate"/>) and rendering, i.e. SQL code generation.</remarks>
    public static class DbColumnExtensions
    {
        #region Render.

        /// <summary>Renders column name for the given DBMS. Handles special characters.</summary>
        /// <param name="column">Column.</param>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which table name is appended.</param>
        /// <remarks>Special characters are handled differently on different DBMSs. 
        /// Eg. if column contains blanks it has to be enclosed in brackets for SQL Server (eg. [Employee ID]).</remarks>
        public static void RenderColumnName(this IDbColumn column, DbmsType dbms, StringBuilder output)
        {
            IColumnRenderer renderer = DbmsComponentFactory.GetComponent<IColumnRenderer>(dbms);
            renderer.RenderColumnName(column, output);
        }

        /// <summary>Renders full name for the given DBMS. Handles special characters. </summary>
        /// <param name="column">Column.</param>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which table name is appended.</param>
        /// <remarks>Special characters in table and column names are handled differently on different DBMSs.</remarks>
        public static void RenderFullName(this IDbColumn column, DbmsType dbms, StringBuilder output)
        {
            if (column.Table != null)
            {
                column.Table.RenderAlias(dbms, output);
                output.Append('.');
            }

            RenderColumnName(column, dbms, output);
        }

        /// <summary>Renders table alias for the given DBMS. Handles special characters. </summary>
        /// <param name="column">Column.</param>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which the alias is appended.</param>
        /// <remarks>Special characters are handled differently on different DBMSs. 
        /// If alias contains blanks it has to be enclosed in brackets for SQL Server 
        /// (eg. [My employees]), in quotes for Oracle (eg. "My employees") etc.</remarks>
        public static void RenderAlias(this IDbColumn column, DbmsType dbms, StringBuilder output)
        {
            IColumnRenderer renderer = DbmsComponentFactory.GetComponent<IColumnRenderer>(dbms);
            renderer.RenderAlias(column, output);
        }

        #endregion

        #region NewDataColumn.

        /// <summary>Creates a new DataColumn that represents this DbColumn in a DataTable.</summary>
        /// <param name="column">Column.</param>
        public static DataColumn NewDataColumn(this IDbColumn column)
        {
            DataColumn col = new DataColumn();
            col.ColumnName = column.ColumnName;
            col.Prefix = GetNormalizedTableAlias(column);
            col.AllowDBNull = column.IsNullable;
            col.DataType = column.DataType;
            col.AutoIncrement = column.AutoIncrement;
            if (column.DefaultValue != null && column.AutoIncrement == false)
                col.DefaultValue = column.DefaultValue;
            if (column.MaxLengthIfText > 0 && column.DataType == typeof(string) || column.DataType == typeof(char[]))
                col.MaxLength = column.MaxLengthIfText;

            return col;
        }

        /// <summary>
        /// Returns normalized table alias that contains only letters and digits.
        /// </summary>
        private static string GetNormalizedTableAlias(IDbColumn column)
        {
            if (column.Table == null)
                return null;

            StringBuilder alias = new StringBuilder();
            string tableAlias = column.Table.Alias;
            foreach (char character in tableAlias)
            {
                if (char.IsLetterOrDigit(character))
                    alias.Append(character);
            }

            return alias.ToString();
        }

        #endregion

        #region Validate.

        /// <summary>
        /// Checks whether the given value is valid for this column.
        /// </summary>
        /// <param name="column">Column.</param>
        /// <remarks>Checks whether the value contained in the given field obeys basic constraints 
        /// (nullability, min/max value etc). Returns <see cref="FieldErrorCode"/> that indicates 
        /// the type of error encountered.</remarks>
        /// <param name="value">Value.</param>
        /// <returns>Error code.</returns>
        /// <example>
        /// The folowing example tests values against the HireDate field of the Northwind's Employees table.
        /// HireDate is a nullable T-SQL DateTime type (SQL Server). Minimum DateTime value supported by SQL Server is January 1, 1753.
        /// <code>
        /// EmployeesMeta employees = new EmployeesMeta();
        /// 
        /// // The field is nullable.
        /// // Validate method returns FieldErrorCode.AllOk.
        /// FieldErrorCode allOk = employees.HireDate.Validate(null);
        /// 
        /// // Minimum DateTime value supported by SQL Server is January 1, 1753.
        /// // Validate method returns FieldErrorCode.OutOfRangeError.
        /// FieldErrorCode outOfRangeError = employees.HireDate.Validate(new DateTime(1000, 1, 1));
        /// 
        /// // The field cannot accept integer values.
        /// // Validate method returns FieldErrorCode.NonCompatibileType.
        /// FieldErrorCode nonCompatibileType = employees.HireDate.Validate(0);
        /// </code>
        /// </example>
        public static FieldErrorCode Validate(this IDbColumn column, object value)
        {
            FieldValidator validator = new FieldValidator(column);
            return validator.Validate(value);
        }

        #endregion
    }
}
